﻿using Microsoft.Extensions.DependencyInjection;
using Polly;
using Polly.Caching;
using Polly.CircuitBreaker;
using Polly.Fallback;
using Polly.Hedging;
using Polly.RateLimiting;
using Polly.Registry;
using Polly.Retry;
using Polly.Timeout;
using System.Text;
using System.Threading.RateLimiting;

namespace PollyDemo
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Samples01();
            Console.WriteLine("程序结束，按任意键退出。");
            Console.ReadKey();

            //var memoryCacheProvider = new MemoryCacheProvider(myMemoryCache);
            //var cachePolicy = Policy.Cache(memoryCacheProvider, TimeSpan.FromMinutes(5));
            //TResult result = cachePolicy.Execute(context => getFoo(), new Context("FooKey"));
            /*
            // Create an instance of builder that exposes various extensions for adding resilience strategies
            ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
                .AddRetry(new Polly.Retry.RetryStrategyOptions()) // Add retry using the default options
                .AddTimeout(TimeSpan.FromSeconds(10)) // Add 10 seconds timeout
                .Build(); // Builds the resilience pipeline

            // await pipeline.ExecuteAsync(static async token => 
            { 
                // your custom logic goes here 
            }, cancellationToken);
            */
        }

        static void Samples01()
        {
            Policy
                // 1. 指定要处理什么异常
                .Handle<HttpRequestException>()
                // 或者指定需要处理什么样的错误返回
                .OrResult<HttpResponseMessage>(r => r.StatusCode == System.Net.HttpStatusCode.BadGateway)
                // 2. 指定重试次数和重试策略
                .Retry(3, (exception, retryCount, context) =>
                {
                    Console.WriteLine($"开始第 {retryCount} 次重试");
                })
                // 3. 执行具体任务
                .Execute(ExecuteMockRequest);
        }

        static HttpResponseMessage ExecuteMockRequest()
        {
            // 模拟网路请求
            Console.WriteLine("正在执行网络请求...");
            Thread.Sleep(3000);

            // 模拟网络错误
            return new HttpResponseMessage(System.Net.HttpStatusCode.BadGateway);
        }


        /// <summary>
        /// 依赖注入策略
        /// </summary>
        static async void Samples02_ServiceProvider()
        {
            var services = new ServiceCollection();

            // Define a resilience pipeline with the name "my-pipeline"
            services.AddResiliencePipeline("my-pipeline", builder =>
            {
                builder
                    .AddRetry(new Polly.Retry.RetryStrategyOptions())
                    .AddTimeout(TimeSpan.FromSeconds(10));
            });

            // Build the service provider
            var serviceProvider = services.BuildServiceProvider();
            // Retrieve a ResiliencePipelineProvider that dynamically creates and caches the resilience pipelines
            var pipelineProvider = serviceProvider.GetRequiredService<ResiliencePipelineProvider<string>>();
            // Retrieve your resilience pipeline using the name it was registered with
            ResiliencePipeline pipeline = pipelineProvider.GetPipeline("my-pipeline");
            // Alternatively, you can use keyed services to retrieve the resilience pipeline
            pipeline = serviceProvider.GetRequiredKeyedService<ResiliencePipeline>("my-pipeline");

            // Execute the pipeline
            await pipeline.ExecuteAsync(static async token =>
            {
                // Your custom logic goes here
            });
        }

        /// <summary>
        /// 重试策略
        /// 出现故障自动重试
        /// </summary>
        static async void Samples03_Retry()
        {
            // Retry using the default options.
            var optionsDefaults = new RetryStrategyOptions();

            // For instant retries with no delay
            var optionsNoDelay = new RetryStrategyOptions()
            {
                Delay = TimeSpan.Zero,
            };
            // For advanced control over the retry behavior, including the number of attempts,
            // delay between retries, and the types of exceptions to handle
            var optionsComplex = new RetryStrategyOptions
            {
                ShouldHandle = new PredicateBuilder().Handle<SomeExceptionType>(),
                BackoffType = DelayBackoffType.Exponential,
                UseJitter = true,
                MaxRetryAttempts = 4,
                Delay = TimeSpan.Zero,
            };

            // To use a custom function to generate the delay for retries
            var optionsDelayGenerator = new RetryStrategyOptions
            {
                MaxRetryAttempts = 2,
                DelayGenerator = static args =>
                {
                    var delay = args.AttemptNumber switch
                    {
                        0 => TimeSpan.Zero,
                        1 => TimeSpan.FromSeconds(1),
                        _ => TimeSpan.FromSeconds(5),
                    };
                    // this example uses a synchronous delay generator
                    // but the API also supports asynchronous inplementations.
                    return new ValueTask<TimeSpan?>(delay);
                }
            };

            // To extract the delay from the result object
            var optionsExtractDelay = new RetryStrategyOptions<HttpResponseMessage>
            {
                DelayGenerator = static args =>
                {
                    /*
                    if (args.Outcome.Result is HttpResponseMessage responseMessage
                        && TryGetDelay(responseMessage, out TimeSpan delay))
                    {
                        return new ValueTask<TimeSpan?>(delay);
                    }*/
                    // Returning null means the retry strategy will use its internal delay for this attempt.
                    return new ValueTask<TimeSpan?>((TimeSpan?)null);
                }
            };

            // To get notifications when a retry is performed
            var optionsOnRetry = new RetryStrategyOptions
            {
                MaxRetryAttempts = 2,
                OnRetry = static args =>
                {
                    Console.WriteLine("OnRetry, Attempt: {0}", args.AttemptNumber);

                    // Event handlers can be asynchronous; here, we return an empty ValueTask.
                    return default;
                }
            };

            // To keep retrying indefinitely or until success use int.MaxValue.
            var optionsIndefiniteRetry = new RetryStrategyOptions
            {
                MaxRetryAttempts = int.MaxValue,
            };

            // Add a retry strategy with a RetryStrategyOptions{<TResult>} instance to the pipeline
            new ResiliencePipelineBuilder().AddRetry(optionsDefaults);
            new ResiliencePipelineBuilder<HttpResponseMessage>().AddRetry(optionsExtractDelay);
        }

        /// <summary>
        /// 断路策略
        /// 不停地重试会加重系统的负担，还可能会影响其他任务。当出错的次数超过了指定的阈值，就要中断当前线路，等待一段时间后再继续
        /// .CircuitBreaker(2, TimeSpan.FromMinutes(1)) 当出现2次异常，等待1分钟后再继续
        /// </summary>
        static async void Samples04_CircuitBreaker() 
        {
            // Circuit breaker with default options.
            var optionsDefaults = new CircuitBreakerStrategyOptions();

            // Circuit breaker with customized options:
            // The circuit will break if more than 50% of actions result in handled exceptions,
            // within any 10-second sampling duration, and at least 8 actions are processed.
            var optionsComplex = new CircuitBreakerStrategyOptions
            {
                FailureRatio = 0.5,
                SamplingDuration = TimeSpan.FromSeconds(10),
                MinimumThroughput = 8,
                BreakDuration = TimeSpan.FromSeconds(30),
                ShouldHandle = new PredicateBuilder().Handle<SomeExceptionType>()
            };

            // Circuit breaker using BreakDurationGenerator:
            // The break duration is dynamically determined based on the properties of BreakDurationGeneratorArguments.
            var optionsBreakDurationGenerator = new CircuitBreakerStrategyOptions
            {
                FailureRatio = 0.5,
                SamplingDuration = TimeSpan.FromSeconds(10),
                MinimumThroughput = 8,
                BreakDurationGenerator = static args => new ValueTask<TimeSpan>(TimeSpan.FromMinutes(args.FailureCount)),
            };

            // Handle specific failed results for HttpResponseMessage:
            var optionsShouldHandle = new CircuitBreakerStrategyOptions<HttpResponseMessage>
            {
                ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
                    .Handle<SomeExceptionType>()
                    .HandleResult(response => response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
            };

            // Monitor the circuit state, useful for health reporting:
            var stateProvider = new CircuitBreakerStateProvider();
            var optionsStateProvider = new CircuitBreakerStrategyOptions<HttpResponseMessage>
            {
                StateProvider = stateProvider,
            };

            var circuitState = stateProvider.CircuitState;

            /*
            CircuitState.Closed - Normal operation; actions are executed.
            CircuitState.Open - Circuit is open; actions are blocked.
            CircuitState.HalfOpen - Recovery state after break duration expires; actions are permitted.
            CircuitState.Isolated - Circuit is manually held open; actions are blocked.
            */

            // Manually control the Circuit Breaker state:
            var manualControl = new CircuitBreakerManualControl();
            var optionsManualControl = new CircuitBreakerStrategyOptions
            {
                ManualControl = manualControl
            };

            // Manually isolate a circuit, e.g., to isolate a downstream service.
            await manualControl.IsolateAsync();

            // Manually close the circuit to allow actions to be executed again.
            await manualControl.CloseAsync();

            // Add a circuit breaker strategy with a CircuitBreakerStrategyOptions{<TResult>} instance to the pipeline
            new ResiliencePipelineBuilder().AddCircuitBreaker(optionsDefaults);
            new ResiliencePipelineBuilder<HttpResponseMessage>().AddCircuitBreaker(optionsStateProvider);

        }

        /// <summary>
        /// 回退策略
        /// 当无法避免的错误发送时，返回值代替失败
        /// .Fallback<UserAvatar>(() => UserAvatar.GetRandomAvatar())
        /// </summary>
        static async void Samples05_Fallback() 
        {
            // A fallback/substitute value if an operation fails.
            var optionsSubstitute = new FallbackStrategyOptions<UserAvatar>
            {
                ShouldHandle = new PredicateBuilder<UserAvatar>()
                    .Handle<SomeExceptionType>()
                    .HandleResult(r => r is null),
                FallbackAction = static args => Outcome.FromResultAsValueTask(UserAvatar.Blank)
            };

            // Use a dynamically generated value if an operation fails.
            var optionsFallbackAction = new FallbackStrategyOptions<UserAvatar>
            {
                ShouldHandle = new PredicateBuilder<UserAvatar>()
                    .Handle<SomeExceptionType>()
                    .HandleResult(r => r is null),
                FallbackAction = static args => 
                {
                    var avatar = UserAvatar.GetRandomAvatar();
                    return Outcome.FromResultAsValueTask<UserAvatar>(avatar);
                }
            };

            // Use a default or dynamically generated value, and execute an additional action if the fallback is triggered.
            var optionsOnFallback = new FallbackStrategyOptions<UserAvatar>
            {
                ShouldHandle = new PredicateBuilder<UserAvatar>()
                    .Handle<SomeExceptionType>()
                    .HandleResult(r => r is null),
                FallbackAction = static args => 
                {
                    var avatar = UserAvatar.GetRandomAvatar();
                    return Outcome.FromResultAsValueTask(UserAvatar.Blank);
                },
                OnFallback = static args => 
                {
                    // Add extra logic to be executed when the fallback is triggered, such as logging.
                    return default;
                }
            };

            // Add a fallback strategy with a FallbackStrategyOptions<TResult> instance to the pipeline
            new ResiliencePipelineBuilder<UserAvatar>().AddFallback(optionsOnFallback);
        }

        /// <summary>
        /// 对冲策略
        /// </summary>
        static async void Samples06_Hedging() 
        {
            // Hedging with default options.
            var optionsDefaults = new HedgingStrategyOptions<HttpResponseMessage>();
            
            // A customized hedging strategy that retries up to 3 times if the execution
            // takes longer than 1 second or if it fails due to an exception or returns an HTTP 500 Internal Server Error.
            var optionsComplex = new HedgingStrategyOptions<HttpResponseMessage>()
            {
                ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
                    .Handle<SomeExceptionType>()
                    .HandleResult(response => response.StatusCode == System.Net.HttpStatusCode.InternalServerError),
                MaxHedgedAttempts = 3,
                Delay = TimeSpan.FromSeconds(1),
                ActionGenerator = static args =>
                {
                    Console.WriteLine("Preparing to execute hedged action.");

                    // Return a delegate function to invoke the original action with the action context.
                    // Optionally, you can also create a completely new action to be executed.
                    return () => args.Callback(args.ActionContext);
                }
            };

            // Subscribe to hedging events.
            var optionsOnHedging = new HedgingStrategyOptions<HttpResponseMessage>
            {
                OnHedging = static args =>
                {
                    Console.WriteLine($"OnHedging: Attempt number {args.AttemptNumber}");
                    return default;
                }
            };

            // Add a hedging strategy with a HedgingStrategyOptions<TResult> instance to the pipeline
            new ResiliencePipelineBuilder<HttpResponseMessage>().AddHedging(optionsDefaults);
        }

        /// <summary>
        /// 超时策略
        /// .Timeout(30, onTimeout: (context, timespan, task) => { // do something }); 超过30秒未完成执行OnTimeout方法
        /// </summary>
        static async void Samples07_Timeout() 
        {
            // To add a timeout with a custom TimeSpan duration
            new ResiliencePipelineBuilder().AddTimeout(TimeSpan.FromSeconds(3));

            // Timeout using the default options.
            var optionsDefaults = new TimeoutStrategyOptions();

            // To add a timeout using a custom timeout generator function
            var optionsTimeoutGenerator = new TimeoutStrategyOptions
            {
                TimeoutGenerator = static args =>
                {
                    // Note: the timeout generator supports asynchronous operations
                    return new ValueTask<TimeSpan>(TimeSpan.FromSeconds(123));
                }
            };

            // To add a timeout and listen for timeout events
            var optionsOnTimeout = new TimeoutStrategyOptions
            {
                TimeoutGenerator = static args =>
                {
                    // Note: the timeout generator supports asynchronous operations
                    return new ValueTask<TimeSpan>(TimeSpan.FromSeconds(123));
                },
                OnTimeout = static args =>
                {
                    Console.WriteLine($"{args.Context.OperationKey}: Execution timed out after {args.Timeout.TotalSeconds} seconds.");
                    return default;
                }
            };

            // Add a timeout strategy with a TimeoutStrategyOptions instance to the pipeline
            new ResiliencePipelineBuilder().AddTimeout(optionsDefaults);
        }

        /// <summary>
        /// 限速器
        /// 并发 队列
        /// </summary>
        static async void Samples08_RateLimiter() 
        {
            // Add rate limiter with default options.
            new ResiliencePipelineBuilder()
                .AddRateLimiter(new RateLimiterStrategyOptions());

            // Create a rate limiter to allow a maximum of 100 concurrent executions and a queue of 50.
            new ResiliencePipelineBuilder()
                .AddConcurrencyLimiter(100, 50);

            // Create a rate limiter that allows 100 executions per minute.
            new ResiliencePipelineBuilder()
                .AddRateLimiter(new SlidingWindowRateLimiter(
                    new SlidingWindowRateLimiterOptions
                    {
                        PermitLimit = 100,
                        Window = TimeSpan.FromMinutes(1)
                    }));
        }
    }
    public class SomeExceptionType : Exception 
    {
        
    }
    public class UserAvatar 
    {
        public static UserAvatar Blank => new UserAvatar();
        public static UserAvatar GetRandomAvatar()
        {
            return new UserAvatar();
        }
    }
}
